#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <memory>
#include <stdexcept>
#include "article.h"
#include "parser.h"
#include "protocol.h"
#include "connection.h"
#include "connectionclosedexception.h"

using namespace std;

const string fail_message = "Client did not follow protocol!";

Parser::Parser(dbinterface* db) {
	this->db = db;
}

vector<unsigned char> Parser::handle_input(const shared_ptr<Connection>& conn) {
	unsigned char command = conn->read();
	vector<unsigned char> output;
	switch (command) {
	
	//1. List newsgroups. The reply contains the number of newsgroups followed by the identiﬁcation numbers and titles of 		//the groups.
	//COM_LIST_NG COM_END
	//ANS_LIST_NG num_p [num_p string_p]* ANS_END
	case Protocol::COM_LIST_NG:
		{ 
			vector<pair<int, string>> groups = db->listnewsgroups();
			unsigned int size = groups.size();
			output.push_back(Protocol::ANS_LIST_NG);
			push_int(output, size);
			for(unsigned int i = 0; i != size; ++i) {
				unsigned int id = groups[i].first;
				string title = groups[i].second;
				push_int(output, id);
				push_string(output, title);
			}
			output.push_back(Protocol::ANS_END);
		}
		break;
	
	//2. Create a newsgroup. The title of the group is sent as a parameter.
	//COM_CREATE_NG string_p COM_END
	//ANS_CREATE_NG [ANS_ACK | ANS_NAK ERR_NG_ALREADY_EXISTS] ANS_END
	case Protocol::COM_CREATE_NG:
		{
		 	string name = capture_string(conn);
			bool success = db->createnewsgroup(name);
			output.push_back(Protocol::ANS_CREATE_NG);
			if(success) {
				output.push_back(Protocol::ANS_ACK);
			} else {
				output.push_back(Protocol::ANS_NAK);
				output.push_back(Protocol::ERR_NG_ALREADY_EXISTS);
			}
			output.push_back(Protocol::ANS_END);
		}
		break;

	//3. Delete a newsgroup. The identiﬁcation number of the group is sent as a parameter.
	//COM_DELETE_NG num_p COM_END
	//ANS_DELETE_NG [ANS_ACK | ANS_NAK ERR_NG_DOES_NOT_EXIST] ANS_END
	case Protocol::COM_DELETE_NG:
		{
		 	unsigned int groupid = capture_int(conn);
			bool success = db->deletenewsgroup(groupid);
			output.push_back(Protocol::ANS_DELETE_NG);
			if(success) {
				output.push_back(Protocol::ANS_ACK);
			} else {
				output.push_back(Protocol::ANS_NAK);
				output.push_back(Protocol::ERR_NG_DOES_NOT_EXIST);
			}
			output.push_back(Protocol::ANS_END);
		}
		break;

	//4. List articles in a newsgroup. The identiﬁcation number of the group is sent as a parameter.
	//The reply contains the number of articles, followed by the identiﬁcation numbers and titles
	//of the articles.
	//COM_LIST_ART num_p COM_END
	//ANS_LIST_ART [ANS_ACK num_p [num_p string_p]* |
	//ANS_NAK ERR_NG_DOES_NOT_EXIST] ANS_END
	case Protocol::COM_LIST_ART:
		{
		 	unsigned int groupid = capture_int(conn);
			pair<vector<pair<int, string>>, int> result = db->listarticles(groupid);
			vector<pair<int, string>> list_of_articles = result.first; 
			int status = result.second; //0 = newsgroup exists. 1 = does not exist
			unsigned int size = list_of_articles.size();
			output.push_back(Protocol::ANS_LIST_ART);
			if(status ==0) {
				output.push_back(Protocol::ANS_ACK);
				push_int(output, size);
				for(auto p : list_of_articles) {
					push_int(output, p.first);
					push_string(output, p.second);
				}				
			} else {
				output.push_back(Protocol::ANS_NAK);
				output.push_back(Protocol::ERR_NG_DOES_NOT_EXIST);
			}
			output.push_back(Protocol::ANS_END);
		}
		break;

	//5. Create an article. The identiﬁcation number of the group is sent as a parameter, followed by
	//the article title, author and text.
	//COM_CREATE_ART num_p string_p string_p string_p COM_END
	//ANS_CREATE_ART [ANS_ACK | ANS_NAK ERR_NG_DOES_NOT_EXIST] ANS_END
	case Protocol::COM_CREATE_ART:
		{
		 	unsigned int groupid = capture_int(conn);
			string info[3];
			for(unsigned int i = 0; i != 3; ++i) {
				info[i] = capture_string(conn);
			}
			bool success = db->createarticle(groupid, info[0], info[1], info[2]);
			output.push_back(Protocol::ANS_CREATE_ART);
			if(success) {
				output.push_back(Protocol::ANS_ACK);
			} else {
				output.push_back(Protocol::ANS_NAK);
				output.push_back(Protocol::ERR_NG_DOES_NOT_EXIST);
			}
			output.push_back(Protocol::ANS_END);
		}
		break;

	//6. Delete an article. The group and article identiﬁcation numbers are sent as parameters.
	//COM_DELETE_ART num_p num_p COM_END
	//ANS_DELETE_ART [ANS_ACK |
	//ANS_NAK [ERR_NG_DOES_NOT_EXIST | ERR_ART_DOES_NOT_EXIST]] ANS_END
	case Protocol::COM_DELETE_ART:
		{
		 	unsigned int groupid = capture_int(conn);
			unsigned int articleid = capture_int(conn);
			int success = db->deletearticle(groupid, articleid);
			output.push_back(Protocol::ANS_DELETE_ART);
			if(success == 0) { //Success
				output.push_back(Protocol::ANS_ACK);
			} else {
				output.push_back(Protocol::ANS_NAK);
				if(success == 1) { //Group not found
					output.push_back(Protocol::ERR_NG_DOES_NOT_EXIST);
				} else { //Article not found
					output.push_back(Protocol::ERR_ART_DOES_NOT_EXIST);
				}
			}
			output.push_back(Protocol::ANS_END);
			
		}
		break;

	//7. Get an article. The group and article identiﬁcation numbers are sent as parameters. The
	//reply contains the title, author, and text of the article.
	//COM_GET_ART num_p num_p COM_END
	//ANS_GET_ART [ANS_ACK string_p string_p string_p |
	//ANS_NAK [ERR_NG_DOES_NOT_EXIST | ERR_ART_DOES_NOT_EXIST]] ANS_END
	case Protocol::COM_GET_ART:
		{
		 	unsigned int groupid = capture_int(conn);
			unsigned int articleid = capture_int(conn);
			pair<int, article> info_pair = db->getarticle(groupid, articleid);
			output.push_back(Protocol::ANS_GET_ART);
			if(info_pair.first == 0) { //Success
				output.push_back(Protocol::ANS_ACK);
				push_string(output, info_pair.second.gettitle());
				push_string(output, info_pair.second.getauthor());
				push_string(output, info_pair.second.gettext());
			} else {
				output.push_back(Protocol::ANS_NAK);
				if(info_pair.first == 1) { //Group not found
					output.push_back(Protocol::ERR_NG_DOES_NOT_EXIST);
				} else { //Article not found
					output.push_back(Protocol::ERR_ART_DOES_NOT_EXIST);
				}
			}
			output.push_back(Protocol::ANS_END);
			
		}
		break;
	default:
		throw invalid_argument(fail_message);
		break;
	}
	command = conn->read(); //Catch the end byte
	if(command != Protocol::COM_END) {
		throw invalid_argument(fail_message);
	}
	return output;
}

//Captures a string_p
string Parser::capture_string(const shared_ptr<Connection>& conn) {
	unsigned char string_p = conn->read();
	if(string_p != Protocol::PAR_STRING) {
		throw invalid_argument(fail_message);
	}
	unsigned int len = parse_int(conn);
	string text = create_string(conn, len);
	return text;

}

//Creates a string (helper method for capture_string)
string Parser::create_string(const shared_ptr<Connection>& conn, unsigned int len) {
	string output;
	for(unsigned int i = 0; i != len; ++i) {
		output += conn->read();
	}
	return output;
}

//Captures a num_p
unsigned int Parser::capture_int(const shared_ptr<Connection>& conn) {
	unsigned char num_p = conn->read();
	if(num_p != Protocol::PAR_NUM) {
		throw invalid_argument(fail_message);
	}
	unsigned int N = parse_int(conn);
	return N;
}

//Creates an unsigned int (helper method for capture_int)
unsigned int Parser::parse_int(const shared_ptr<Connection>& conn) {
	unsigned char byte1 = conn->read();
	unsigned char byte2 = conn->read();
	unsigned char byte3 = conn->read();
	unsigned char byte4 = conn->read();
	return (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4;
}

//Splits an int and pushes it onto an output NOT including PAR_NUM flag
void Parser::push_int_no_flag(vector<unsigned char> &output, unsigned int input) {
	output.push_back(input >> 24);
	output.push_back(input >> 16);
	output.push_back(input >> 8);
	output.push_back(input);
}

//Splits an int and pushes it onto an output, following protocol num_p
void Parser::push_int(vector<unsigned char> &output, unsigned int input) {
	output.push_back(Protocol::PAR_NUM);
	push_int_no_flag(output, input);
}
 
//Pushes a string onto output vector, following protocol string_p
void Parser::push_string(vector<unsigned char> &output, string input) {
	output.push_back(Protocol::PAR_STRING);
	unsigned int len = input.size();
	push_int_no_flag(output,len);
	for(unsigned char c : input) {
		output.push_back(c);
	}
}

string Parser::translate(unsigned char c) {
	string reply;
	switch(c) {
	
	case(Protocol::COM_LIST_NG):
		reply = "COM_LIST_NG";
		break;
	case(Protocol::COM_CREATE_NG):
		reply = "COM_CREATE_NG";
		break;
	case(Protocol::COM_DELETE_NG):
		reply = "COM_DELETE_NG";
		break;
	case(Protocol::COM_LIST_ART):
		reply = "COM_LIST_ART";
		break;
	case(Protocol::COM_CREATE_ART):
		reply = "COM_CREATE_ART";
		break;
	case(Protocol::COM_DELETE_ART):
		reply = "COM_DELETE_ART";
		break;
	case(Protocol::COM_GET_ART):
		reply = "COM_GET_ART";
		break;
	case(Protocol::COM_END):
		reply = "COM_END";
		break;

	case(Protocol::ANS_LIST_NG):
		reply = "ANS_LIST_NG";
		break;
	case(Protocol::ANS_CREATE_NG):
		reply = "ANS_CREATE_NG";
		break;
	case(Protocol::ANS_DELETE_NG):
		reply = "ANS_DELETE_NG";
		break;
	case(Protocol::ANS_LIST_ART):
		reply = "ANS_LIST_ART";
		break;
	case(Protocol::ANS_CREATE_ART):
		reply = "ANS_CREATE_ART";
		break;
	case(Protocol::ANS_DELETE_ART):
		reply = "ANS_DELETE_ART";
		break;
	case(Protocol::ANS_GET_ART):
		reply = "ANS_GET_ART";
		break;
	case(Protocol::ANS_END):
		reply = "ANS_END";
		break;
	case(Protocol::ANS_ACK):
		reply = "ANS_ACK";
		break;
	case(Protocol::ANS_NAK):
		reply = "ANS_NAK";
		break;

	case(Protocol::PAR_STRING):
		reply = "PAR_STRING";
		break;
	case(Protocol::PAR_NUM):
		reply = "PAR_NUM";
		break;

	case(Protocol::ERR_NG_ALREADY_EXISTS):
		reply = "ERR_NG_ALREADY_EXISTS";
		break;
	case(Protocol::ERR_NG_DOES_NOT_EXIST):
		reply = "ERR_NG_DOES_NOT_EXIST";
		break;
	case(Protocol::ERR_ART_DOES_NOT_EXIST):
		reply = "ERR_ART_DOES_NOT_EXIST";
		break;
	default:
		reply = static_cast<unsigned int>(c);
		break;
	}
	return reply;
}





